package xyz.lwm.lazycat.beans;

import org.jetbrains.annotations.NotNull;
import xyz.lwm.lazycat.annotation.Primary;
import xyz.lwm.lazycat.utility.Assert;
import xyz.lwm.lazycat.utility.It;
import xyz.lwm.lazycat.utility.StringUtil;
import xyz.lwm.lazycat.utility.convert.Types;
import xyz.lwm.lazycat.utility.reflect.TypeUtil;

import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Optional;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.stream.Collectors;

/**
 * BeanRefs
 *
 * @author lwm
 */
public final class BeanRefs {

    // key: bean name, value: bean ref
    private final Map<String, BeanRef<?>> beanRefMap = new ConcurrentHashMap<>();

    /**
     * put bean ref
     *
     * @param beanName bean name, also design.name
     * @param beanRef  bean ref
     */
    public <T> void put(String beanName, BeanRef<T> beanRef) {
        beanRefMap.put(beanName, beanRef);
    }

    /**
     * get bean ref by bean name
     *
     * @param beanName bean name, also design.name
     */
    @SuppressWarnings("unchecked")
    public <T> BeanRef<T> get(String beanName) {
        return (BeanRef<T>) beanRefMap.get(beanName);
    }

    /**
     * get bean ref by dependency
     *
     * @param dependency dependsOn or injection
     * @return bean ref
     */
    public BeanRef<?> get(@NotNull Dependency dependency) {
        Class<?> owner = dependency.getOwner();
        String beanName = dependency.getName();
        Type type = dependency.getType();

        // get bean ref by bean name
        if (StringUtil.isNotBlank(beanName)) {
            BeanRef<?> ref = get(beanName);
            Assert.isTrue(ref != null,
                    "dependency bean not exist, name: {}, type: {}",
                    beanName, type.getTypeName());
            return ref;
        }

        // get bean ref by dependency type
        Class<?> clz = TypeUtil.getClass(type);
        if (Types.isBeanType(clz) && !Map.class.isAssignableFrom(clz)) {

            Map<String, ? extends BeanRef<?>> map = map(ref -> clz
                            .isAssignableFrom(ref.getDesign().getType()),
                    ref -> ref);
            // cannot be empty
            Assert.isTrue(!map.isEmpty(),
                    "dependency bean not exist, type: {}", clz.getName());
            // primary bean
            Optional<? extends BeanRef<?>> first = map.values().stream().filter(ref ->
                    ref.getDesign().getAnnotations().hasAnnotation(Primary.class)).findFirst();
            if (first.isPresent()) {
                return first.get();
            }
            // check bean type unique
            Assert.isTrue(map.size() == 1,
                    StringUtil.format("bean type not unique, there are {} beans for {}.", map.size(), clz.getName()));
            return map.values().iterator().next();

        } else if (Types.isCollectionType(clz) || Types.isArrayType(clz)) {

            // get item class from array component type or collection generic type
            Class<?> itemClz = Types.isArrayType(clz) ? clz.getComponentType() :
                    It.of(TypeUtil.getTypeArgument(type, 0)).let(TypeUtil::getClass).getOr(null);
            // item class must not be Object.class
            Assert.isTrue(itemClz != null && itemClz != Object.class,
                    "unknown generic type of dependency type {} in class {}",
                    type.getTypeName(), owner.getName());
            // item class must be bean type
            Assert.isTrue(Types.isBeanType(itemClz),
                    "dependency type not support: {}", type.getTypeName());
            Map<String, ? extends BeanRef<?>> refMap = map(ref ->
                            itemClz.isAssignableFrom(ref.getDesign().getType()),
                    ref -> ref);
            return new ListBeanRef(new ArrayList<>(refMap.values()));

        } else {
            throw new BeansException(StringUtil.format("dependency type not support: {}", type.getTypeName()));
        }
    }

    /**
     * 根据服务类型获取服务 SERVICE MAP
     *
     * @param filter ref filter
     * @param mapper ref mapper
     * @return 服务实例 LinkedHashMap, MAP 可能为空 map, 但不会 null
     */
    @NotNull
    public <T> Map<String, T> map(@NotNull Predicate<BeanRef<?>> filter, @NotNull Function<BeanRef<?>, T> mapper) {
        return beanRefMap.values().stream().filter(filter)
                .sorted(Comparator.comparingInt(ref -> ref.getDesign().getOrder()))
                .collect(Collectors.toMap(ref -> ref.getDesign().getName(), mapper,  (a, b) -> a, LinkedHashMap::new));
    }


    void remove(String beanName) {
        beanRefMap.remove(beanName);
    }

}
